﻿using DeepEarth.Core.Data;
using System.Collections.Generic;
using System.Windows;
using System;
using DeepEarth.Core;
using System.Windows.Controls;
using System.Windows.Threading;
using DeepEarth.Map.Core.Clustering;
namespace DeepEarth.Map.Core.Filters
{
    public class Synchronizer
    {
        public Synchronizer(Func<Location, Point> locationToViewportPoint)
        {
            this.LocationToViewportPoint = locationToViewportPoint;
        }

        public void Filter(double viewportWidth, List<ClusterViewModel> elementsToSynchronize, Dictionary<ClusterViewModel, UIElement> allElements)
        {
            if (viewportWidth <= 0)
            {
                return;
            }

            foreach (var viewModel in elementsToSynchronize)
            {
                // Multithreaded - check required
                if (allElements.ContainsKey(viewModel))
                {
                    UIElement element = allElements[viewModel];
                    SynchronizeElement(element, viewportWidth);
                }
            }
        }

        private Func<Location, Point> locationToViewportPoint;
        public Func<Location, Point> LocationToViewportPoint
        {
            get
            {
                return locationToViewportPoint;
            }
            set
            {
                locationToViewportPoint = value;
            }
        }

        public void SynchronizeElement(UIElement element, double viewportWidth)
        {
            Point locatedPoint = new Point(0, 0);
            var location = Layer.GetPosition(element);
            if (location != null)
            {
                var p = LocationToViewportPoint(location);

                PositionOrigin origin = Layer.GetPositionOrigin(element);

                double offsetX = 0;
                double offsetY = 0;

                // Don't calculate an offset if one is not required.
                if (origin.X != 0 && origin.Y != 0)
                {
                    FrameworkElement fe = element as FrameworkElement;
                    if (fe != null)
                    {
                        double width = fe.ActualWidth;
                        double height = fe.ActualHeight;
                        if (width == 0)
                        {
                            fe.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                            width = fe.DesiredSize.Width;
                            height = fe.DesiredSize.Height;
                        }

                        offsetX = width * origin.X;
                        offsetY = height * origin.Y;
                    }

                    if (element is MapItemAdorner)
                    {
                        MapItemAdorner mei = element as MapItemAdorner;
                        mei.PositionOriginOffsetApplied(offsetX, offsetY);
                    }
                }

                locatedPoint.X = p.X - offsetX;
                locatedPoint.Y = p.Y - offsetY;
                Canvas.SetLeft(element, locatedPoint.X);
                Canvas.SetTop(element, locatedPoint.Y);
            }

            // ISelfPositionings may not have an attached location so they are always part of the visual tree
            // This needs to be optimised. i.e. only arrange element if they are currently visible
            if (element is ISelfPositioning)
            {
                Point offset = new Point(-locatedPoint.X, -locatedPoint.Y);

                ((ISelfPositioning)element).Refresh(offset, 16848);
            }

            // TODO: Only scale if visible
            if (element is IScalable)
            {
                IScalable scalable = element as IScalable;
                if (!scalable.MaintainConstantSize)
                {
                    double zoomFactor = Math.Log(1 / viewportWidth, 2);
                    double scale = Math.Pow(0.02 * (zoomFactor + 1), 2) + 0.01;

                    (scalable).ApplyScale(scale, 1.0);
                }
            }

        }
    }
}
